using Pegasus.Diagnostics;
using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;

using Pegasus.Log4Net;

namespace Pegasus.Diagnostics.Telnet
{
	/// <summary>
	/// Use this class to allow remote command line connections to
	/// an application or a service.  This will allow us to
	/// configure, debug, and manipulate the application (or
	/// service) while it is up and running.
	/// </summary>
	/// <remarks>
	/// This class can be started on a background thread to listen for remote
	/// connections or it can take over the execution of an application or
	/// services main thread.  If a connection is seen, the socket is connected up
	/// to a command line interpreter for the application that controls this
	/// Command Manager object.  This will allow us to connect to an application
	/// (or service) from a remote host and configure/debug/manipulate that app.
	/// <para>
	/// Connecting to an application or service that uses the TelnetService
	/// us done using the telnet command.  Specifically, you use specify
	/// the server name or ip address and the port that the application is
	/// listening on.  Here are some examples:
	/// </para>
	/// <para>
	/// telnet localhost 11000<br />
	/// telnet 10.6.1.151 12500<br />
	/// telnet ancnews01.myfamilysouth.com 13000<br />
	/// </para>
	/// </remarks>
	public class TelnetService
	{
		// Local Instance Values
		private ILog m_log = LogManager.GetLogger( typeof( TelnetService ) );

		private int m_connectionPort = 0;
		private bool m_listening = true;

		private TcpListener m_listenerSocket = null;
		private Thread m_listenerThread = null;
		private ArrayList m_customCommands = new ArrayList();

		/// <summary>
		/// This is the default constructor for the command manager.  It sets the
		/// port that we will be listening on to the telnet port 23.
		/// </summary>
		public TelnetService()
		{
			m_connectionPort = 23;
		}

		/// <summary>
		/// This is the default constructor for the command manager.  It sets the
		/// port that we will be listening on.
		/// </summary>
		/// <param name="port">The port to listen for connections on.</param>
		public TelnetService( int port )
		{
			// Check params
			ParamCode.AssertRange( port, IPEndPoint.MinPort, IPEndPoint.MaxPort, "port" );

			m_connectionPort = port;
		}

		/// <summary>
		/// Handles starting the RemoteCmdManager object on a background thread
		/// where it will listen for clients that would like to establish a connection
		/// to this application.
		/// </summary>
		public void Start()
		{
//			DebugCode.AssertNull( m_listenerThread );
			if( m_listenerThread == null )
			{
				// Create a new TCP Listener that will listen for clients on the specified port
				// using any ip stacks installed on this machine.
//				DebugCode.AssertNull( m_listenerSocket );
				IPEndPoint endPoint = new IPEndPoint( IPAddress.Any, m_connectionPort );
				m_listenerSocket = new TcpListener( endPoint );

				m_listenerThread = new Thread( new ThreadStart( Execute ) );
				m_listenerThread.Name = "Telnet Service Thread: " + GetHashCode();
				m_listenerThread.Start();
			}
			else
			{
				m_log.Warn( "Start() Listener thread should already be running." );
			}

			return;
		}

		/// <summary>
		/// This will tell the RemoteCmdManager to stop listening and exit
		/// it's thread.
		/// </summary>
		public void Stop()
		{
			// Stop the thread that is listening for new client connections and exit.
			m_listening = false;

			m_log.Debug( "Stop() Stopping the connection listener." );

//			DebugCode.AssertNotNull( m_listenerSocket );
//			DebugCode.AssertNotNull( m_listenerThread );
			if( m_listenerSocket != null )
			{
				try
				{
					m_listenerSocket.Stop();
				}
				catch( SocketException eSocket )
				{
					m_log.Error( "Stop() caught a Socket Exception trying to close TCP Listener", eSocket );
				}
			}

			// We are no longer listening for client connections so release
			// the socket and thread objects.
			m_listenerSocket = null;
			m_listenerThread = null;
		}

		/// <summary>
		/// Add a custom command (object that implements ITelnetCommand).
		/// </summary>
		/// <param name="command">The new command object</param>
		public void AddCommand( ITelnetCommand command )
		{
			m_customCommands.Add( command );
		}

		/// <summary>
		/// This is an override to produce a reasonable text string value
		/// for debugging.
		/// </summary>
		/// <returns>
		/// A string that show the internal values of the object.
		/// </returns>
		public override String ToString()
		{
			StringBuilder str = new StringBuilder();
			str.Append( "TelnetService " )
				.Append( GetHashCode() )
				.Append( ", listening: " )
				.Append( m_listening )
				.Append( ", port: " )
				.Append( m_connectionPort )
				.Append( " Listener Socket: " );

			if( m_listenerSocket == null )
			{
				str.Append( "NULL" );
			}
			else
			{
				str.Append( m_listenerSocket );
			}

			return str.ToString();
		}

		/// <summary>
		/// This method is called to set up the connection listener and
		/// take over the current executing thread.
		/// </summary>
		private void Execute()
		{
			try
			{
//				DebugCode.AssertNotNull( m_listenerSocket );
//				DebugCode.AssertNotNull( m_listenerThread );

				m_log.DebugFormat( "Execute() Opening TCP Server Socket on port: {0}", m_connectionPort );

				// Start the TCP Listener up.
				m_listenerSocket.Start();

				m_log.DebugFormat( "Execute() TCP Listener opened and listening for clients on port: {0}", m_connectionPort );
			}
			catch( Exception e )
			{
				m_log.Error( "Execute() General exception starting TCP Listener.", e );
				return;
			}

			try
			{
				while( m_listening )
				{
					// Accept new connections on this socket.
					TcpClient tcpClient = m_listenerSocket.AcceptTcpClient();

					m_log.DebugFormat( "Execute() Accepted new client connection - {0}", tcpClient );
					new TelnetProcessor( tcpClient, m_customCommands );
				}
			}
			catch( SocketException e )
			{
				m_log.WarnFormat( "Execute() SocketException - we are assuming that this means the Socket has been closed by the StopConnectionListener method. {0}", e.Message );
			}
			catch( Exception e )
			{
				m_log.Error( "Execute() General exception reading from TCP Listener.", e );
			}
		}

		/// <summary>
		/// Allows the caller to perform a join with this background thread and make sure
		/// this thread finishes before we exit.
		/// </summary>
		private void Join()
		{
			if( null == m_listenerThread )
			{
				m_log.Warn( "Join() Listener thread is null." );

				// Return false to indicate we did not join the thread.
				return;
			}

			m_listenerThread.Join();

			return;
		}
	}
}
